#!/usr/bin/python
# -*- coding: utf-8 -*-

#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.


import select
import logging
import os
import sys
import xmpp

__version__ = '0.1'

class Error(Exception):
    """Base error class"""
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)

class ConnectionError(Error):
    """Error while connecting to server"""
    pass

class AuthError(Error):
    """Authentification error"""
    pass

class ConnectionLostError(Error):
    """Connection lost"""
    pass

class Account(object):
    """Account object.
    
    jid is jabber id in form user@server
    callback is function wich is called on messages received.
    if callback returns None nothing happens.
    if callback returns string it is sent back.
    """
    def __init__(self, jid, password, callback):
        self.jid = jid
        self.password = password
        self.callback = callback

class Bots(object):
    """Simplr bots factory.
    
    You should initialize it with accounts list (Account object).
    """
    def __init__(self, accounts, resource='XMPPbot', version=__version__):
        """accounts is a list of Account objects"""
        self.resource=resource
        self.version=version
        uname=os.popen("uname -sr", 'r')
        self.os=uname.read().strip() + '/Python %s' % sys.version
        uname.close()
        self.conns = []
        for account in accounts:
            jid = xmpp.JID(account.jid)
            user, server = jid.getNode(), jid.getDomain()
            password = account.password
            callback = account.callback
            conn = xmpp.Client(server, debug=[])
            conres = conn.connect()
            if not conres:
                raise ConnectionError("Unable to connect to server %s!" % server)

            if conres <> 'tls':
                logging.info("Unable to estabilish secure connection - TLS failed!")
            authres = conn.auth(user, password, self.resource)
            if not authres:
                raise AuthError("Unable to authorize on %s@%s - check login/password." % (user, server))
            if authres <> 'sasl':
                logging.info("Unable to perform SASL auth os %s. Old authentication method used!" % server)
            conn.RegisterHandler('message',
                                 self._message_handler_generator(callback))
            conn.RegisterHandler('presence', self._presence)
            conn.RegisterHandler('iq', self._version, typ='get',
                                 ns=xmpp.NS_VERSION)
            conn.sendInitPresence()
            self.conns.append(conn)
        self.online = True

    def _message_handler_generator(self, callback):
        """Generates message handler for specified callback"""
        def message_handler(conn, message):
            text = message.getBody()
            user = message.getFrom()
            type_ = message.getType()
            logging.debug('Message (%s): %s: %s' % 
                          (unicode(type_).encode('utf-8'), 
                           unicode(user).encode('utf-8'),
                           unicode(text).encode('utf-8')))
            # Is it really chat? Not a state notification?
            if type_ in ['normal', 'chat', None] and not text is None:
                reply = callback(text)
                if not reply is None:
                    conn.send(xmpp.Message(user, reply, typ='chat'))
        return message_handler

    def _presence(self, conn, pres):
        """Grants authorisation on a subscribe requests"""
        ptype = pres.getType()
        who = pres.getFrom()
        if ptype == 'subscribe':
            conn.send(xmpp.Presence(who, 'subscribed'))

    def _version(self, conn, iq):
        """Returns reply to iq:version"""
        iq=iq.buildReply('result')
        qp=iq.getTag('query')
        qp.setTagData('name', self.resource)
        qp.setTagData('version', self.version)
        qp.setTagData('os', self.os)
        conn.send(iq)
        raise xmpp.NodeProcessed

    def run(self):
        """Starts main loop"""
        socketlist = {}
        for conn in self.conns:
            socketlist[conn.Connection._sock] = conn
        #This is main select loop.
        while self.online:
            (i , o, e) = select.select(socketlist.keys(), [], [], 1)
            for sock in i:
                r=socketlist[sock].Process(1)
                # IOError or connection is closed.
                if r==None or r==0:
                    raise ConnectionLostError('Connection lost.')


